package com.kgcx.dbutils.interceptor;

import com.alibaba.fastjson.util.TypeUtils;
import com.kgcx.common.utils.CollectionUtils;
import com.kgcx.common.utils.RequestParamUtil;
import com.kgcx.common.utils.SensitiveUtils;
import com.kgcx.dbutils.annotation.SelectBetweenAnd;
import com.kgcx.dbutils.annotation.SelectExactType;
import com.xiaoleilu.hutool.bean.BeanUtil;
import com.xiaoleilu.hutool.util.ReflectUtil;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * User : wuganglin
 * <p>
 * Date : 2017/12/1 11:30
 * version $Id: QueryInterceptor.java, v 0.1 Exp $
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
public class QueryInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {


        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        /**
         * 此插件目的是给sql动态添加参数,必须先于分页插件执行
         * 因为分页插件会手动生产BoundSql执行,在这之后通过setParameter的拦截器动态添加的参数是无法生效的
         */
        if (null != parameter) {
            sensitive(parameter);
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            if (null != requestAttributes) {
                Class clazz = parameter.getClass();
                if (!(parameter instanceof Map)) {
                    //非bean非map参数不处理,例如根据主键查询,此时parameter就是个Long
                    if (BeanUtil.isBean(clazz)) {
                        //把参数对象硬转成map,这样就可以随意添加参数,不受bean属性的限制
                        parameter = BeanUtil.beanToMap(parameter);
                    }
                }
                if (parameter instanceof Map) {

                    HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

                    //把request中的参数全部放入map,目的是把非bean属性的参数引入,例如cityIds
                    // 注意,一定要采用只添加不覆盖的方式
                    //因为上面的代码转入的参数是被spring mvc针对bean处理过的,是有精确类型的(如Long,Timestamp)
                    //但通过request添加的全部是String类型,在mybatis解析时时需要精确类型的,否则会报错
                    putAll((Map) parameter, RequestParamUtil.getAllRequestParam(request), clazz);

                    //通过oa网关进来的请求,会附带cityIds做数据隔离,此处需要统一把cityIds传到sql的where中去
                    //具体拼接的where条件在SelectActiveProvider的sqlCityIds方法中
                    if (StringUtils.isNotBlank(request.getParameter("cityIds"))) {
                        ((Map) parameter).put("cityIds", request.getParameter("cityIds").split(","));
                    }
                }
            }
        }


        //由于逻辑关系，只会进入一次
        if (args.length == 4) {
            //4 个参数时
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }

        //注：下面的方法可以根据自己的逻辑调用多次，在分页插件中，count 和 page 各调用了一次
        Object result = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        if (null != result) {
            CollectionUtils.runOnObjectOrCollection(result, SensitiveUtils::decryptObject);
        }

        return result;

    }

    private void sensitive(Object o) {
        if (BeanUtil.isBean(o.getClass())) {
            SensitiveUtils.encryptObject(o);
        }
    }

    /**
     * key存在则不添加覆盖
     *
     * @param m
     * @param a
     */
    private void putAll(Map m, Map a, Class clazz) {
        if (MapUtils.isNotEmpty(a)) {
            Field[] fields = BeanUtil.isBean(clazz) ? ReflectUtil.getFields(clazz) : null;
            a.forEach((k, v) -> {
                //key存在则不添加覆盖,保持原来的精确类型
                if (!m.containsKey(k)) {
                    Object value = v;
                    if (ArrayUtils.isNotEmpty(fields)) {
                        for (Field field : fields) {
                            //如果是时间段查询的参数,则转成对应的格式,比如Timestamp
                            Annotation[] declaredAnnotations = field.getDeclaredAnnotations();
                            if (ArrayUtils.isNotEmpty(declaredAnnotations)) {
                                for (Annotation declaredAnnotation : declaredAnnotations) {
                                    if (declaredAnnotation.annotationType().isAnnotationPresent(SelectExactType.class)) {
                                        List<String> suffixList = new ArrayList<>();
                                        Object suffix = AnnotationUtils.getValue(declaredAnnotation, "suffix");
                                        if (null != suffix) {
                                            suffixList.add(suffix.toString());
                                        }
                                        if (declaredAnnotation instanceof SelectBetweenAnd) {
                                            Object betweenSuffix = AnnotationUtils.getValue(declaredAnnotation, "betweenSuffix");
                                            Object andSuffix = AnnotationUtils.getValue(declaredAnnotation, "andSuffix");
                                            if (null != betweenSuffix) {
                                                suffixList.add(betweenSuffix.toString());
                                            }
                                            if (null != andSuffix) {
                                                suffixList.add(andSuffix.toString());
                                            }
                                        }
                                        if (CollectionUtils.isNotEmpty(suffixList)) {
                                            if (StringUtils.endsWithAny(k.toString(), suffixList.toArray(new String[suffixList.size()]))) {
                                                value = TypeUtils.cast(v, field.getType(), null);
                                            }
                                        }

                                    }
                                }
                            }
//                            if (field.isAnnotationPresent(SelectBetweenAnd.class)) {
//                                SelectBetweenAnd selectBetweenAnd = field.getAnnotation(SelectBetweenAnd.class);
//                                String betweenSuffix = selectBetweenAnd.betweenSuffix();
//                                String andSuffix = selectBetweenAnd.andSuffix();
//                                if (StringUtils.endsWithAny(k.toString(), betweenSuffix, andSuffix)) {
//                                    value = TypeUtils.cast(v, field.getType(), null);
//                                }
//                            }
//                            if (field.isAnnotationPresent(SelectGreaterThan.class)) {
//                                SelectGreaterThan selectGreaterThan = field.getAnnotation(SelectGreaterThan.class);
//                                String suffix = selectGreaterThan.suffix();
//                                if (StringUtils.endsWithAny(k.toString(), suffix)) {
//                                    value = TypeUtils.cast(v, field.getType(), null);
//                                }
//                            }
//                            if (field.isAnnotationPresent(SelectLessThan.class)) {
//                                SelectLessThan selectLessThan = field.getAnnotation(SelectLessThan.class);
//                                String suffix = selectLessThan.suffix();
//                                if (StringUtils.endsWithAny(k.toString(), suffix)) {
//                                    value = TypeUtils.cast(v, field.getType(), null);
//                                }
//                            }
                        }
                    }
                    m.put(k, value);
                }
            });
        }
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}